/**
 * Wrapper class for Motor2 Rigid Body Physics library
 * Code monkey: al@stdio.co.uk
 */


package {
	
	import de.polygonal.motor2.World;
	import de.polygonal.motor2.collision.shapes.LineShape;
	import de.polygonal.motor2.collision.shapes.ShapeSkeleton;
	import de.polygonal.motor2.collision.shapes.ShapeTypes;
	import de.polygonal.motor2.collision.shapes.data.BoxData;
	import de.polygonal.motor2.collision.shapes.data.CircleData;
	import de.polygonal.motor2.collision.shapes.data.LineData;
	import de.polygonal.motor2.collision.shapes.data.PolyData;
	import de.polygonal.motor2.dynamics.RigidBody;
	import de.polygonal.motor2.dynamics.RigidBodyData;
	import de.polygonal.motor2.math.AABB2;
	import de.polygonal.motor2.math.V2;
	
	import flash.display.Graphics;
	import flash.geom.Point;
	
	
	
	public class Physics {
		
		// How much simulation time elapses between each call to step()
		private static const TIME_STEP:Number = 0.2;
		// How many times the collision detection code is run  with each call to step()
		private static const NUM_ITERATIONS:int = 20;
		// Initial gravity settings (0,0 for top-down)
		private static const GRAVITY_X:Number = 0;
		private static const GRAVITY_Y:Number = 0;
		// Default properties for rigid bodies that are created
		private static const DEFAULT_FRICTION:Number = 0.1;
		private static const DEFAULT_RESTITUTION:Number = 0.1;
		
		
		// Wireframe rendering styles
		private static const STROKE_THICKNESS:Number = 0;
		private static const STROKE_COLOUR:uint = 0xFFFFFF;
		private static const FILL_COLOUR:Number = 0xFFFFFF;
		private static const FILL_ALPHA:Number = 0.5;
		
		
		
		// The world controlling the physics simultion
		private var _world:World;
		// Dimensions of the simulation world
		private var width:Number;
		private var height:Number;
		
		
		
		public function Physics(worldWidth:int, worldHeight:int) {
			width = worldWidth;
			height = worldHeight;
			
			initWorld();
		}
		
		
		public function step():void {
			// Call this method to update the physics simulation.
			// Generally this will be called via and ENTER_FRAME
			// event handler or a Timer.
			_world.step(TIME_STEP, NUM_ITERATIONS);
		}
		
		
		public function get world():World {
			return _world;
		}
		
		
		private function initWorld():void {
			// Create a world that is larger than the supplied width and height
			// dimensions. This allows 'walls' to be built around the world
			// using boxes without the boxes becoming frozen. If a rigid body
			// goes outside of the world's bounds it is frozen and is no longer
			// part of the simulation. If other bodies touch a frozen body then
			// they too become frozen and sleep for a thousand years until a 
			// handsome prince comes along and wakes them up.
			var pad:Number = 100;
			var bounds:AABB2 = new AABB2(-pad, -pad, width+pad, height+pad);
			
			// Allowing bodies to sleep can speed up the calculations in the
			// simulation. When a body stops moving it is put to sleep and then
			// only included in the simulation if it comes into contact with another
			// body.
			var allowSleep:Boolean = false;
			
			// Instantiate the world.
			_world = new World(bounds, allowSleep);
			
			// Give the world some gravity. Gravity is just a universally applied force
			// which acts on all rigid bodies according to their mass.
			_world.setGravity(GRAVITY_X, GRAVITY_Y);
			
			// Create some walls around the edge of the world so that bodies can't go 
			// off the edge of the simulation and become frozen like some loose peas down
			// the back of your freezer.
			var halfWidth:Number = 0.5 * width;
			var halfHeight:Number = 0.5 * height;
			var halfPad:Number = 0.5 * pad;
			var doublePad:Number = 2 * pad;
			// Left
			addBox(-halfPad, halfHeight, pad, height+doublePad);
			// Right
			addBox(width+halfPad, halfHeight, pad, height+doublePad);
			// Top
			addBox(halfWidth, -halfPad, width+doublePad, pad);
			// Bottom
			addBox(halfWidth, height+halfPad, width+doublePad, pad);
		}
		
		
		
		
		/****************************************************************************************** 
		 * Notes about group, category and mask values:
		 * 
		 * 1. 	When the groupIndex of both shapes is negative, the
		 * 		shapes never collide and the maskBits/categoryBits have no effect.
		 * 
		 * 2.	When the groupIndex of both shapes is equal and positive, the
		 * 		shapes always collide and the maskBits/categoryBits have no effect.
		 * 
		 * 3. 	When the groupIndex of both shapes is different and positive, the
		 * 		shapes collide if the maskBits/categoryBits check is successful.
		 * 
		 * 4. 	When the groupIndex of both shapes is zero, the groupIndex has no
		 * 		effect and only the maskBits/categoryBits are checked.
		 * 
		 * Mask bits filter out the category bits for both shapes. The default value
		 * for mask bits is 0xFFFF, meaning that no category bits are filtered out.
		 * 
		 * The two shapes have to have one matching category bit between them (along
		 * with the matching bit in each of their masks turned on) for a collision to
		 * occur.
		 * 
		 * 
		 * Examples:
		 * 
		 * SHAPE1 mask:0xFFFE, category:0x0001
		 * SHAPE2 mask:0xFFFF, category:0x0001
		 * No collision occurs since SHAPE1's mask bits filter out the one matching
		 * category bit.
		 * 
		 * SHAPE1 mask:0xFFFE, category:0x0002
		 * SHAPE2 mask:0xFFFF, category:0x0003
		 * Collision occurs since both shapes share the category bit 2 (in binary,
		 * their category bits are ...000010 and ....000011 respectively).
		 *****************************************************************************************/
		
		 /*****************************************************************************************
		 * RIGID BODY CREATORS
		 *****************************************************************************************/
		
		 /**
		 * Creates a RigidBody containing a single rectangular shape
		 */
		 public function addBox(	cx:Number,
		 							cy:Number,
		 							width:Number,
		 							height:Number, 
		 							rotation:Number=0,
		 							density:Number=0,
		 							friction:Number=DEFAULT_FRICTION,
		 							restitution:Number=DEFAULT_RESTITUTION,
		 							preventRotation:Boolean=false,
		 							group:int=0,
		 							category:uint=0x0001,
		 							mask:uint=0xFFFF
		 							
		 						):RigidBody {
		 							
		 	var bd:BoxData = createBoxData(width, height, 0, 0, 0, density, friction, restitution, group, category, mask);						
			var rbd:RigidBodyData = new RigidBodyData(cx, cy, rotation);
			rbd.preventRotation = preventRotation;
			rbd.addShapeData(bd);
			
			return world.createBody(rbd);
		 }
		 
		 
		 /**
		 * Creates a RigidBody containing a single circular shape
		 */
		 public function addCircle(	cx:Number,
		 							cy:Number,
		 							radius:Number,
		 							density:Number=0,
		 							friction:Number=DEFAULT_FRICTION,
		 							restitution:Number=DEFAULT_RESTITUTION,
		 							preventRotation:Boolean=false,
		 							group:int=0,
		 							category:uint=0x0001,
		 							mask:uint=0xFFFF
		 							
		 						):RigidBody {
		 	
		 	var cd:CircleData = createCircleData(radius, 0, 0, density, friction, restitution, group, category, mask);
		 	var rbd:RigidBodyData = new RigidBodyData(cx, cy);
			rbd.preventRotation = preventRotation;
		 	rbd.addShapeData(cd);
		 	
		 	return world.createBody(rbd);
		 }
		 
		 
		 /**
		 * Creates a RigidBody containing an n-sided polygon
		 * Generally polygons with up to 8 sides can be created - 
		 * see Constants.k_maxPolyVertices.
		 */
		 public function addPoly(	cx:Number,
		 							cy:Number,
		 							vertexList:Vector.<Number>, 
		 							rotation:Number=0,
		 							density:Number=0,
		 							friction:Number=DEFAULT_FRICTION,
		 							restitution:Number=DEFAULT_RESTITUTION,
		 							preventRotation:Boolean=false,
		 							group:int=0,
		 							category:uint=0x0001,
		 							mask:uint=0xFFFF
		 							
		 						):RigidBody {
		 							
		 	var pd:PolyData = createPolyData(vertexList, 0, 0, 0, density, friction, restitution, group, category, mask);						
			var rbd:RigidBodyData = new RigidBodyData(cx, cy, rotation);
			rbd.preventRotation = preventRotation;
			rbd.addShapeData(pd);
			
			return world.createBody(rbd);
		 }
		 
		 
		 /**
		 * Creates a RigidBody based on a single straight line
		 */
		 public function addLine(	x0:Number, 
		 							y0:Number,
		 							x1:Number,
		 							y1:Number,
		 							density:Number=0,
		 							friction:Number=DEFAULT_FRICTION,
		 							restitution:Number=DEFAULT_RESTITUTION,
		 							doubleSided:Boolean=true,
		 							infinite:Boolean=false,
		 							preventRotation:Boolean=false,
		 							group:int=0,
		 							category:uint=0x0001,
		 							mask:uint=0xFFFF
		 							
		 						):RigidBody {
		 							
		 	
		 	var ld:LineData = createLineData(0, 0, x1-x0, y1-y0, 0, density, friction, restitution, doubleSided, infinite, group, category, mask);
		 	var rbd:RigidBodyData = new RigidBodyData(0.5*(x0+x1), 0.5*(y0+y1));
			rbd.preventRotation = preventRotation;
		 	rbd.addShapeData(ld);
		 	
		 	return world.createBody(rbd);
		 }
		 
		 
		 /**
		 * Creates a RigidBody based on a list of shape data instances
		 */
		 public function addCompoundShape(	cx:Number,
				 							cy:Number,
				 							shapeDataList:Array,
				 							rotation:Number=0,
				 							preventRotation:Boolean=false
				 							
				 						):RigidBody {
		 							
		 	var rbd:RigidBodyData = new RigidBodyData(cx, cy, rotation);
			rbd.preventRotation = preventRotation;
			var count:int = shapeDataList.length;
			for (var i:int=0; i<count; i++) {
				rbd.addShapeData(shapeDataList[i]);
			}
			
			return world.createBody(rbd);
		}
		
		
		
		
		
		
		
		
		/******************************************************************************************
		* SHAPE DATA CREATORS
		******************************************************************************************/
		
		/**
		* Creates a BoxData instance. This can then be used to
		* make a compound RigidBody made up of several shapes
		*/
		public function createBoxData(	width:Number,
			 							height:Number,
			 							xOffset:Number=0,
			 							yOffset:Number=0,
			 							rotationOffset:Number=0,
			 							density:Number=0,
			 							friction:Number=DEFAULT_FRICTION,
			 							restitution:Number=DEFAULT_RESTITUTION,
			 							group:int=0,
			 							category:uint=0x0001,
			 							mask:uint=0xFFFF
			 							
			 						):BoxData {
		 	
		 	var bd:BoxData = new BoxData(density, width, height);
		 	bd.mx = xOffset;
		 	bd.my = yOffset;
		 	bd.mr = rotationOffset;
		 	bd.friction = friction;
		 	bd.restitution = restitution;
			bd.groupIndex = group;
			bd.categoryBits = category;
			bd.maskBits = mask;
			
		 	return bd;
		}
		 
		 
		/**
		* Creates a CircleData instance. This can then be used to
		* make a compound RigidBody made up of several shapes
		*/
		public function createCircleData(	radius:Number,
				 							xOffset:Number=0,
				 							yOffset:Number=0,
				 							density:Number=0,
				 							friction:Number=DEFAULT_FRICTION,
				 							restitution:Number=DEFAULT_RESTITUTION,
				 							group:int=0,
				 							category:uint=0x0001,
				 							mask:uint=0xFFFF
				 							
			 							):CircleData {
		 	
		 	var cd:CircleData = new CircleData(density, radius);
		 	cd.mx = xOffset;
		 	cd.my = yOffset;
		 	cd.friction = friction;
		 	cd.restitution = restitution;
			cd.groupIndex = group;
			cd.categoryBits = category;
			cd.maskBits = mask;
			
		 	return cd;
		 }
		 
		 
		 /**
		 * Creates a BoxData instance. This can then be used to
		 * make a compound RigidBody made up of several shapes
		 */
		 public function createPolyData(	vertexList:Vector.<Number>,
		 									xOffset:Number=0,
				 							yOffset:Number=0,
				 							rotationOffset:Number=0,
				 							density:Number=0,
				 							friction:Number=DEFAULT_FRICTION,
				 							restitution:Number=DEFAULT_RESTITUTION,
				 							group:int=0,
				 							category:uint=0x0001,
				 							mask:uint=0xFFFF
				 							
				 						):PolyData {
		 	
		 	var pd:PolyData = new PolyData(density, vertexList);
		 	pd.mx = xOffset;
		 	pd.my = yOffset;
		 	pd.mr = rotationOffset;
		 	pd.friction = friction;
		 	pd.restitution = restitution;
			pd.groupIndex = group;
			pd.categoryBits = category;
			pd.maskBits = mask;
			
		 	return pd;
		 }
		 
		 
		/**
		* Creates a LineData instance. This can then be used to
		* make a compound RigidBody made up of several shapes
		*/
		public function createLineData(	x0:Number, 
				 							y0:Number,
				 							x1:Number,
				 							y1:Number,
				 							rotationOffset:Number=0,
				 							density:Number=0,
				 							friction:Number=DEFAULT_FRICTION,
				 							restitution:Number=DEFAULT_RESTITUTION,
				 							doubleSided:Boolean=true,
				 							infinite:Boolean=false,
				 							group:int=0,
				 							category:uint=0x0001,
				 							mask:uint=0xFFFF
				 							
										):LineData {
											
			var ptA:Point = new Point(0, 0);
		 	var ptB:Point = new Point(x1-x0, y1-y0);
		 	var ld:LineData = new LineData(ptA, ptB, infinite, doubleSided);
		 	ld.density = density;
		 	ld.friction = friction;
		 	ld.restitution = restitution;
		 	ld.groupIndex = group;
		 	ld.categoryBits = category;
		 	ld.maskBits = mask;
		 	ld.mx = x0;
		 	ld.my = y0;
		 	ld.mr = rotationOffset;
		 	return ld;
		}
		 
		 
		 
		 
		 
		 
		 
		 
		 
		 
		/******************************************************************************************
		* DEBUG WIREFRAME DRAWING METHODS
		******************************************************************************************/
		
		public function drawWireframes(g:Graphics):void {
			var shapeList:Vector.<ShapeSkeleton> = world.getShapeList();
			var numShapes:int = shapeList.length;
			var s:ShapeSkeleton;
			var i:int;
			
			g.clear();
			g.lineStyle(STROKE_THICKNESS, STROKE_COLOUR);
			for (i=0; i<numShapes; i++) {
				s = shapeList[i];
				g.beginFill(FILL_COLOUR, FILL_ALPHA);
				drawShape(s, g);
				g.endFill();
			}
			g.lineStyle();
		}
		
		
		protected function drawShape(s:ShapeSkeleton, g:Graphics):void {
			// Transform the shape's vertices into world space so we can render the shape		
			s.toWorldSpace();
			
			var v:V2 = s.worldVertexChain;
			
			if (s.type == ShapeTypes.CIRCLE) {
				g.moveTo(s.x, s.y);
				g.drawCircle(s.x, s.y, s.radius);
				drawShapeCentre(s.x, s.y, s.r11, s.r21, g);
			}
			else if (s.type == ShapeTypes.LINE && LineShape(s).infinite) {
				var d:V2 = s.modelVertexChain.edge.d;
				g.moveTo(v.x - d.x * 1000, v.y - d.y * 1000);
				v = v.next;
				g.lineTo(v.x + d.x * 1000, v.y + d.y * 1000);
			}			
			else {
				g.moveTo(v.x, v.y);
				while (true) {
					g.lineTo(v.x, v.y);
					if (v.isTail) {
						g.lineTo(v.next.x, v.next.y);
						break;
					}
					v = v.next;
				}
				if (s.type != ShapeTypes.LINE) {
					drawShapeCentre(s.x, s.y, s.r11, s.r21, g);
				}
			}
		}
		
		
		protected function drawShapeCentre(cx:Number, cy:Number, rSin:Number, rCos:Number, g:Graphics):void {
			var len:Number = 10;
			rSin *= len;
			rCos *= len;

			/*g.moveTo(cx, cy);
			g.lineTo(cx+rCos, cy-rSin);
			g.moveTo(cx, cy);
			g.lineTo(cx-rCos, cy+rSin);*/
			g.moveTo(cx, cy);
			g.lineTo(cx+rSin, cy+rCos);
			//g.moveTo(cx, cy);
			//g.lineTo(cx-rSin, cy-rCos);
		}
	}
}